package com.yupi.springbootinit.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.constant.CommonConstant;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.exception.ThrowUtils;
import com.yupi.springbootinit.mapper.*;
import com.yupi.springbootinit.model.dto.processsheet.ProcessSheetAddRequest;
import com.yupi.springbootinit.model.dto.processsheet.ProcessSheetExcelStepDTO;
import com.yupi.springbootinit.model.dto.processsheet.ProcessSheetQueryRequest;
import com.yupi.springbootinit.model.dto.processsheet.ProcessSheetUpdateRequest;
import com.yupi.springbootinit.model.entity.*;
import com.yupi.springbootinit.model.vo.ProcessSheetVO;
import com.yupi.springbootinit.service.*;
import com.yupi.springbootinit.utils.SqlUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 工艺单服务实现
 */
@Service
@Slf4j
public class ProcessSheetServiceImpl extends ServiceImpl<ProcessSheetMapper, ProcessSheet> implements ProcessSheetService {

    @Resource
    private WorkflowTaskService workflowTaskService;

    @Resource
    private BatchCodeGeneratorService batchCodeGeneratorService;

    @Resource
    private DeviceInfoMapper deviceInfoMapper;

    @Resource
    private ProcessBatchMapper processBatchMapper;

    @Resource
    private ProcessBatchStepMapper processBatchStepMapper;

    @Resource
    private WorkflowTaskMapper workflowTaskMapper;

    @Resource
    private SalesOrderItemMapper salesOrderItemMapper;

    @Resource
    private SalesOrderService salesOrderService;

    @Resource
    private ProcessSheetStepMapper processSheetStepMapper;

    @Resource
    private OperationMapper operationMapper;

    @Resource
    private SalesOrderMapper salesOrderMapper;

    @Resource
    private ProcessSheetProductMapper processSheetProductMapper;

    @Resource
    private ProcessSheetBatchMapper processSheetBatchMapper;

    @Resource
    private ProcessSheetBatchProductMapper processSheetBatchProductMapper;

    @Resource
    private DocumentParseService documentParseService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createProcessSheetFromTask(String taskId, ProcessSheetAddRequest request, User user) {
        log.info("========== 开始从任务创建工艺单 ==========");
        log.info("taskId={}, userId={}, userName={}", taskId, user.getId(), user.getUserName());

        // 1. 验证并认领任务
        WorkflowTask task = validateAndClaimTask(taskId, user.getId(), user.getUserName(), user.getDepartmentId());

        // 2. 验证业务类型
        if (!"SALES_ORDER".equals(task.getBizType()) && !"RDO_ORDER".equals(task.getBizType())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,
                    "任务业务类型不支持，仅支持销售订单或研发订单");
        }

        // 3. 自动填充订单基本信息
        if (StringUtils.isBlank(request.getSourceId())) {
            request.setSourceId(String.valueOf(task.getBizId()));
        }
        if (StringUtils.isBlank(request.getSourceNo())) {
            request.setSourceNo(task.getBizNumber());
        }
        if ("SALES_ORDER".equals(task.getBizType())) {
            request.setSourceType(1);
        } else if ("RDO_ORDER".equals(task.getBizType())) {
            request.setSourceType(2);
        }

        // 4. 创建工艺单
        Long sheetId = createWithSteps(request, user.getId(), user.getUserName(), user.getDepartmentId());

        // 5. 完成工程部任务
        Long taskIdLong;
        try {
            taskIdLong = Long.parseLong(taskId);
        } catch (NumberFormatException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "任务ID格式错误");
        }

        // ✅ 拼接完整工艺单号
        String fullProcessSheetNo = batchCodeGeneratorService.buildFullProcessSheetNo(
                request.getProcessSheetNo(),
                request.getProcessVersion()
        );

        boolean completed = workflowTaskService.completeTask(
                taskIdLong,
                user.getId(),
                user.getUserName(),
                "工艺单创建完成，工艺单号：" + fullProcessSheetNo
        );

        if (!completed) {
            log.warn("完成任务失败，但工艺单已创建: taskId={}, sheetId={}", taskId, sheetId);
        }

        // 6. 更新订单状态
        try {
            salesOrderService.updateOrderStatus(
                    task.getBizId(),
                    "ROUTING_PUBLISHED",
                    "工艺已发布",
                    20.0
            );
        } catch (Exception e) {
            log.error("更新订单状态失败: orderId={}", task.getBizId(), e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "更新订单状态失败");
        }

        // ========== 🔧 修改点1：父任务获取或创建（防重复） ==========
        Long pmcParentTaskId = getOrCreatePmcParentTask(task, user);

        // ========== 🔧 修改点2：子任务创建（增强异常处理） ==========
        createPmcBatchSubTasks(sheetId, pmcParentTaskId, user);

        log.info("========== 工艺单创建流程完成 ==========");
        log.info("sheetId={}, orderId={}, 父任务ID={}", sheetId, task.getBizId(), pmcParentTaskId);

        return sheetId;
    }

    /**
     * 🔧 新增方法：获取或创建PMC父任务（确保一个订单只有一个父任务）
     */
    private Long getOrCreatePmcParentTask(WorkflowTask orderTask, User user) {
        log.info("========== 开始获取或创建PMC父任务 ==========");
        log.info("orderId={}, orderNumber={}", orderTask.getBizId(), orderTask.getBizNumber());

        // ========== 1. 先查询是否已存在父任务（使用 LambdaQueryWrapper）==========
        WorkflowTask existingParentTask = workflowTaskMapper.selectOne(
                new LambdaQueryWrapper<WorkflowTask>()
                        .eq(WorkflowTask::getBizType, "SALES_ORDER")
                        .eq(WorkflowTask::getBizId, orderTask.getBizId())
                        .eq(WorkflowTask::getTaskType, "CREATE_SCHEDULE")
                        .eq(WorkflowTask::getIsDelete, 0)
                        .isNull(WorkflowTask::getParentTaskId)
                        .in(WorkflowTask::getTaskStatus, Arrays.asList("PENDING", "IN_PROGRESS"))
                        .orderByDesc(WorkflowTask::getCreateTime)  // ✅ 类型安全
                        .last("LIMIT 1")
        );

        if (existingParentTask != null) {
            log.info("✅ 复用已存在的PMC父任务: parentTaskId={}, orderId={}, status={}",
                    existingParentTask.getId(), orderTask.getBizId(), existingParentTask.getTaskStatus());
            return existingParentTask.getId();
        }

        // ========== 2. 不存在则创建新的父任务 ==========
        log.info("未找到已存在的父任务，开始创建新的父任务");

        String productName = getProductNameSummary(orderTask.getBizId());

        try {
            workflowTaskService.createScheduleTaskForSalesOrder(
                    orderTask.getBizId(),
                    orderTask.getBizNumber(),
                    productName,
                    user.getId(),
                    user.getUserName()
            );
        } catch (Exception e) {
            log.error("❌ 创建PMC父任务失败: orderId={}", orderTask.getBizId(), e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR,
                    "创建PMC父任务失败: " + e.getMessage());
        }

        // ========== 3. 查询刚创建的父任务（使用 LambdaQueryWrapper）==========
        WorkflowTask newParentTask = workflowTaskMapper.selectOne(
                new LambdaQueryWrapper<WorkflowTask>()
                        .eq(WorkflowTask::getBizType, "SALES_ORDER")
                        .eq(WorkflowTask::getBizId, orderTask.getBizId())
                        .eq(WorkflowTask::getTaskType, "CREATE_SCHEDULE")
                        .eq(WorkflowTask::getIsDelete, 0)
                        .isNull(WorkflowTask::getParentTaskId)
                        .orderByDesc(WorkflowTask::getCreateTime)  // ✅ 类型安全
                        .last("LIMIT 1")
        );

        if (newParentTask == null) {
            log.error("❌ 创建PMC父任务后未能查询到: orderId={}", orderTask.getBizId());
            throw new BusinessException(ErrorCode.OPERATION_ERROR,
                    "PMC父任务创建失败，请联系管理员");
        }

        log.info("✅ PMC父任务创建成功: parentTaskId={}, orderId={}",
                newParentTask.getId(), orderTask.getBizId());

        return newParentTask.getId();
    }

    /**
     * 🔧 新增方法：为每个批次创建PMC子任务
     */
    private void createPmcBatchSubTasks(Long processSheetId, Long parentTaskId, User user) {
        log.info("========== 开始创建批次子任务 ==========");
        log.info("processSheetId={}, parentTaskId={}", processSheetId, parentTaskId);

        // ========== 1. 查询批次记录 ==========
        List<ProcessBatch> batches = processBatchMapper.selectList(
                new LambdaQueryWrapper<ProcessBatch>()
                        .eq(ProcessBatch::getProcessSheetId, processSheetId)
                        .eq(ProcessBatch::getIsDeleted, 0)
                        .orderByAsc(ProcessBatch::getBatchCode)
        );

        if (batches == null || batches.isEmpty()) {
            log.warn("⚠️ 未找到批次记录: processSheetId={}", processSheetId);
            // 🔧 修改：抛出异常（批次是必需的）
            throw new BusinessException(ErrorCode.OPERATION_ERROR,
                    "未找到批次记录，无法创建子任务");
        }

        log.info("✅ 查询到批次记录: processSheetId={}, 批次数量={}", processSheetId, batches.size());

        // ========== 2. 为每个批次创建子任务 ==========
        for (ProcessBatch batch : batches) {
            try {
                workflowTaskService.createPublishBatchTaskForBatch(
                        batch.getId(),
                        batch.getOrderNumber(),
                        batch.getBatchCode(),
                        parentTaskId,
                        user.getId(),
                        user.getUserName()
                );

                log.info("✅ 批次子任务创建成功: batchId={}, batchCode={}, parentTaskId={}",
                        batch.getId(), batch.getBatchCode(), parentTaskId);

            } catch (Exception e) {
                log.error("❌ 创建批次子任务失败: batchId={}, batchCode={}",
                        batch.getId(), batch.getBatchCode(), e);
                // 🔧 修改：抛出异常，利用事务回滚
                throw new BusinessException(ErrorCode.OPERATION_ERROR,
                        "创建批次子任务失败: " + e.getMessage());
            }
        }

        log.info("✅ 所有批次子任务创建完成: processSheetId={}, 批次数量={}, 父任务ID={}",
                processSheetId, batches.size(), parentTaskId);
    }

    /**
     * 验证并认领任务
     */
    private WorkflowTask validateAndClaimTask(String taskId, Long userId, String userName, Long departmentId) {
        Long taskIdLong;
        try {
            taskIdLong = Long.parseLong(taskId);
        } catch (NumberFormatException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "任务ID格式错误");
        }

        WorkflowTask task = workflowTaskMapper.selectById(taskIdLong);

        if (task == null || (task.getIsDelete() != null && task.getIsDelete() == 1)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "任务不存在");
        }

        String status = task.getTaskStatus();
        if ("DONE".equals(status)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "任务已完成，不能重复创建工艺单");
        }

        if (!departmentId.equals(task.getDepartmentId())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR,
                    "您不属于该任务的归属部门，无权操作");
        }

        if ("PENDING".equals(status)) {
            boolean claimed = workflowTaskService.claimTask(taskIdLong, userId, userName, departmentId);
            if (!claimed) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "认领任务失败");
            }
            log.info("任务认领成功: taskId={}, userId={}", taskId, userId);
        } else if ("IN_PROGRESS".equals(status)) {
            if (!userId.equals(task.getClaimedBy())) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR,
                        "该任务已被其他人认领：" + task.getClaimedByName());
            }
            log.info("任务已认领: taskId={}, claimedBy={}", taskId, task.getClaimedByName());
        }

        return task;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createWithSteps(ProcessSheetAddRequest request, Long creatorId, String creatorName, Long departmentId) {
        if (request == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数不能为空");
        }

        log.info("开始创建工艺单: sourceType={}, sourceId={}", request.getSourceType(), request.getSourceId());

        // ✅ 批次必填校验
        if (request.getBatches() == null || request.getBatches().isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "批次计划不能为空，请至少设置一个批次");
        }

        // ========== 1. 构建工艺单头表 ==========
        ProcessSheet processSheet = new ProcessSheet();

        // 基本信息
        processSheet.setSourceType(request.getSourceType() != null ? request.getSourceType() : 1);
        processSheet.setSourceId(StringUtils.isNotBlank(request.getSourceId())
                ? Long.parseLong(request.getSourceId()) : null);
        processSheet.setSourceNo(request.getSourceNo());

        // ✅ 工艺版次（默认A）
        processSheet.setProcessVersion(StringUtils.isNotBlank(request.getProcessVersion())
                ? request.getProcessVersion() : "A");

        // 材料信息
        processSheet.setMaterialBrand(request.getMaterialBrand());
        processSheet.setMaterialSpec(request.getMaterialSpec());
        processSheet.setPowderSupplier(request.getPowderSupplier());

        // ✅ 自动统计批次数量
        processSheet.setPlannedBatchCount(request.getBatches().size());

        // 其他字段
        processSheet.setDueDate(request.getDueDate());
        processSheet.setTechnicalRequirements(request.getTechnicalRequirements());
        processSheet.setProcessFileIds(request.getProcessFileIds());
        processSheet.setCustomerName(request.getCustomerName());
        processSheet.setCreatedBy(creatorId);
        processSheet.setCreatedByName(creatorName);
        processSheet.setDepartmentId(departmentId);

        // ========== 🔧 修改点3：设备信息处理（前端只传 deviceId） ==========
        if (request.getDeviceId() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请选择生产设备");
        }

        try {
            DeviceInfo device = deviceInfoMapper.selectById(request.getDeviceId());
            if (device == null) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR,
                        "设备不存在或已删除: deviceId=" + request.getDeviceId());
            }

            // 设置设备信息（冗余存储）
            processSheet.setDeviceId(device.getId());
            processSheet.setDeviceCode(device.getDeviceCode());
            processSheet.setDeviceName(device.getDeviceName());

            log.info("✅ 设备信息设置成功: deviceId={}, deviceCode={}, deviceName={}",
                    device.getId(), device.getDeviceCode(), device.getDeviceName());

        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("查询设备信息失败: deviceId={}", request.getDeviceId(), e);
            throw new BusinessException(ErrorCode.OPERATION_ERROR,
                    "查询设备信息失败: " + e.getMessage());
        }

        // ✅ 状态改为已发布
        processSheet.setProcessStatus(1);

        Integer sourceType = processSheet.getSourceType();
        if (sourceType != 1 && sourceType != 2) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "来源类型错误，应为1-销售订单或2-研发订单");
        }

        // ========== 2. 查询订单并填充产品明细 ==========
        List<SalesOrderItem> orderItems = null;
        SalesOrder salesOrder = null;

        if (sourceType == 1) {
            Long srcId = processSheet.getSourceId();
            String srcNo = processSheet.getSourceNo();

            if (srcId == null && StringUtils.isBlank(srcNo)) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "销售订单必须提供 sourceId 或 sourceNo");
            }

            if (srcId != null) {
                salesOrder = salesOrderMapper.selectById(srcId);
            } else {
                salesOrder = salesOrderMapper.selectOne(
                        new QueryWrapper<SalesOrder>()
                                .eq("orderNumber", srcNo)
                                .eq("is_deleted", 0)
                );
            }

            if (salesOrder == null) {
                throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "销售订单不存在");
            }

            processSheet.setSourceId(salesOrder.getId());
            processSheet.setSourceNo(salesOrder.getOrderNumber());
            processSheet.setCustomerName(salesOrder.getCustomerName());
            processSheet.setDueDate(salesOrder.getDueDate() != null
                    ? salesOrder.getDueDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()
                    : null);

            // 查询订单明细
            orderItems = salesOrderItemMapper.selectList(
                    new LambdaQueryWrapper<SalesOrderItem>()
                            .eq(SalesOrderItem::getOrderId, salesOrder.getId())
                            .eq(SalesOrderItem::getIsDeleted, 0)
                            .orderByAsc(SalesOrderItem::getSortOrder)
            );

            if (orderItems == null || orderItems.isEmpty()) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "订单没有产品明细，无法创建工艺单");
            }

            log.info("订单产品明细: orderId={}, 产品数量={}", salesOrder.getId(), orderItems.size());

            // 填充冗余字段（第一个产品）
            SalesOrderItem firstItem = orderItems.get(0);
            processSheet.setProductCode(firstItem.getProductCode());
            processSheet.setProductName(firstItem.getProductName());
            processSheet.setSpecification(firstItem.getSpecification());

            // 计算总数量
            int totalQuantity = orderItems.stream()
                    .mapToInt(item -> item.getQuantity() != null ? item.getQuantity() : 0)
                    .sum();
            processSheet.setQuantity(totalQuantity);
            processSheet.setProductCount(orderItems.size());

        } else {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "研发订单暂未支持，请选择销售订单");
        }

        // ========== 3. 生成工艺单基础号（不含版本后缀） ==========
        if (StringUtils.isBlank(processSheet.getProcessSheetNo())) {
            String baseNo = generateProcessSheetBaseNo();
            processSheet.setProcessSheetNo(baseNo);
        }

        // ========== 4. 检查基础号+版本号的唯一性 ==========
        long existCount = this.count(new QueryWrapper<ProcessSheet>()
                .eq("process_sheet_no", processSheet.getProcessSheetNo())
                .eq("process_version", processSheet.getProcessVersion())
                .eq("is_deleted", 0));
        if (existCount > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,
                    "工艺单号已存在：" + batchCodeGeneratorService.buildFullProcessSheetNo(
                            processSheet.getProcessSheetNo(),
                            processSheet.getProcessVersion()
                    ));
        }

        // ========== 5. 保存工艺单头表 ==========
        validProcessSheet(processSheet, true);
        boolean result = this.save(processSheet);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "保存工艺单失败");

        Long sheetId = processSheet.getId();
        String fullNo = batchCodeGeneratorService.buildFullProcessSheetNo(
                processSheet.getProcessSheetNo(),
                processSheet.getProcessVersion()
        );
        log.info("工艺单头表保存成功: id={}, fullProcessSheetNo={}, deviceCode={}",
                sheetId, fullNo, processSheet.getDeviceCode());

        // ========== 6. 保存产品明细 ==========
        if (request.getProducts() != null && !request.getProducts().isEmpty()) {
            // 前端传入产品（含零件图号、单件重量）
            saveProductDetails(sheetId, request.getProducts());
        } else if (orderItems != null && !orderItems.isEmpty()) {
            // ✅ 从订单自动生成产品明细（零件图号和单件重量留空）
            saveProductDetailsFromOrder(sheetId, orderItems);
        }

        // ========== 7. 保存工序步骤 ==========
        List<ProcessSheetAddRequest.ProcessSheetStepAddRequest> stepsReq = request.getSteps();
        if (stepsReq != null && !stepsReq.isEmpty()) {
            saveProcessSteps(sheetId, stepsReq);
        }

        // ========== 8. 保存批次计划（同时创建批次记录和批次编码） ==========
        saveBatchPlans(sheetId, request.getBatches(), creatorId, creatorName);

        // 将request中的基础号和版本号回写（用于后续流程）
        request.setProcessSheetNo(processSheet.getProcessSheetNo());
        request.setProcessVersion(processSheet.getProcessVersion());

        log.info("工艺单创建成功: id={}, fullProcessSheetNo={}, productCount={}, batchCount={}",
                sheetId, fullNo, processSheet.getProductCount(), processSheet.getPlannedBatchCount());

        return sheetId;
    }

    /**
     * 保存产品明细（前端传入，包含零件图号、单件重量）
     */
    private void saveProductDetails(Long sheetId, List<ProcessSheetAddRequest.ProductDetailRequest> products) {
        // ✅ 新增调试日志
        log.info("========== 开始保存产品明细 ==========");
        log.info("processSheetId: {}, 产品数量: {}", sheetId, products.size());
        for (int i = 0; i < products.size(); i++) {
            ProcessSheetAddRequest.ProductDetailRequest req = products.get(i);

            // ✅ 打印每个产品的详细信息
            log.info("产品 #{}: productCode={}, productName={}, specification={}, quantity={}, " +
                            "productHeight={}, partDrawingNo={}, unitWeight={}",
                    i + 1,
                    req.getProductCode(),
                    req.getProductName(),
                    req.getSpecification(),
                    req.getQuantity(),
                    req.getProductHeight(),
                    req.getPartDrawingNo(),
                    req.getUnitWeight()
            );

            ProcessSheetProduct product = new ProcessSheetProduct();
            product.setProcessSheetId(sheetId);
            product.setOrderItemId(StringUtils.isNotBlank(req.getOrderItemId())
                    ? Long.parseLong(req.getOrderItemId()) : null);
            product.setProductCode(req.getProductCode());
            product.setProductName(req.getProductName());
            product.setSpecification(req.getSpecification());
            product.setQuantity(req.getQuantity());
            product.setPartDrawingNo(req.getPartDrawingNo());
            product.setUnitWeight(req.getUnitWeight());
            product.setProductHeight(req.getProductHeight());
            product.setSortOrder(req.getSortOrder() != null ? req.getSortOrder() : (i + 1));

            int inserted = processSheetProductMapper.insert(product);
            ThrowUtils.throwIf(inserted != 1, ErrorCode.OPERATION_ERROR, "保存产品明细失败");
        }
        log.info("产品明细保存成功: processSheetId={}, 产品数量={}", sheetId, products.size());
    }

    /**
     * ✅ 从订单自动生成产品明细（零件图号和单件重量留空，由工程部后续填写）
     */
    private void saveProductDetailsFromOrder(Long sheetId, List<SalesOrderItem> orderItems) {
        int sortOrder = 0;
        for (SalesOrderItem item : orderItems) {
            sortOrder++;
            ProcessSheetProduct product = new ProcessSheetProduct();
            product.setProcessSheetId(sheetId);
            product.setOrderItemId(item.getId());
            product.setProductCode(item.getProductCode());
            product.setProductName(item.getProductName());
            product.setSpecification(item.getSpecification());
            product.setQuantity(item.getQuantity());
            product.setSortOrder(sortOrder);

            // ✅ 零件图号和单件重量留空，由工程部手动填写
            product.setPartDrawingNo(null);
            product.setUnitWeight(null);

            int inserted = processSheetProductMapper.insert(product);
            ThrowUtils.throwIf(inserted != 1, ErrorCode.OPERATION_ERROR, "保存产品明细失败");
        }
        log.info("从订单生成产品明细成功: processSheetId={}, 产品数量={}", sheetId, orderItems.size());
    }

    /**
     * 保存工序步骤
     */
    private void saveProcessSteps(Long sheetId, List<ProcessSheetAddRequest.ProcessSheetStepAddRequest> stepsReq) {
        // 预加载工序信息
        Set<Long> opIds = stepsReq.stream()
                .map(ProcessSheetAddRequest.ProcessSheetStepAddRequest::getOperationId)
                .filter(StringUtils::isNotBlank)
                .map(Long::parseLong)
                .collect(Collectors.toSet());

        Map<Long, Operation> opMap = Collections.emptyMap();
        if (!opIds.isEmpty()) {
            List<Operation> ops = operationMapper.selectBatchIds(opIds);
            if (ops != null && !ops.isEmpty()) {
                opMap = ops.stream().collect(Collectors.toMap(Operation::getId, o -> o));
            }
        }

        int index = 0;
        for (ProcessSheetAddRequest.ProcessSheetStepAddRequest s : stepsReq) {
            index++;
            ProcessSheetStep step = new ProcessSheetStep();
            step.setProcessSheetId(sheetId);
            Integer seq = s.getSequenceNo();
            step.setSequenceNo(seq != null && seq > 0 ? seq : index);

            if (StringUtils.isNotBlank(s.getOperationId())) {
                step.setOperationId(Long.parseLong(s.getOperationId()));
            }

            String opName = StringUtils.trimToNull(s.getOperationName());
            ThrowUtils.throwIf(StringUtils.isBlank(opName), ErrorCode.PARAMS_ERROR, "工序名称不能为空");
            step.setOperationName(opName);

            if (step.getOperationId() != null) {
                Operation op = opMap.get(step.getOperationId());
                if (op != null) {
                    step.setOperationCode(op.getOperationCode());
                }
            }

            step.setIsInspectionRequired(s.getIsInspectionRequired() != null ? s.getIsInspectionRequired() : 0);
            step.setInspectionMethod(s.getInspectionMethod());
            step.setInspectionCriteria(s.getInspectionCriteria());
            step.setIsOutsourced(s.getIsOutsourced() != null ? s.getIsOutsourced() : 0);
            step.setProcessRequirement(s.getProcessRequirement());

            int inserted = processSheetStepMapper.insert(step);
            ThrowUtils.throwIf(inserted != 1, ErrorCode.OPERATION_ERROR, "保存工艺步骤失败");
        }
        log.info("工序步骤保存成功: processSheetId={}, 步骤数量={}", sheetId, stepsReq.size());
    }

    /**
     * 保存批次计划（同时创建批次记录和批次编码）
     */
    private void saveBatchPlans(Long sheetId,
                                List<ProcessSheetAddRequest.BatchPlanRequest> batches,
                                Long creatorId,
                                String creatorName) {

        if (batches == null || batches.isEmpty()) {
            log.warn("批次计划为空，跳过保存: sheetId={}", sheetId);
            return;
        }

        // ========== 1. 查询工艺单信息（获取设备信息） ==========
        ProcessSheet sheet = this.getById(sheetId);
        if (sheet == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "工艺单不存在");
        }

        if (sheet.getDeviceId() == null || StringUtils.isBlank(sheet.getDeviceCode())) {
            log.error("❌ 工艺单未设置设备信息: processSheetId={}, deviceId={}, deviceCode={}",
                    sheetId, sheet.getDeviceId(), sheet.getDeviceCode());
            throw new BusinessException(ErrorCode.PARAMS_ERROR,
                    "工艺单未设置设备，无法生成批次编码，请在工艺单表单中选择设备");
        }

        log.info("✅ 工艺单设备信息: deviceId={}, deviceCode={}, deviceName={}",
                sheet.getDeviceId(), sheet.getDeviceCode(), sheet.getDeviceName());

        // ========== 🔧 新增：预查询工艺单产品明细（用于验证 product_id） ==========
        List<ProcessSheetProduct> sheetProducts = processSheetProductMapper.selectList(
                new LambdaQueryWrapper<ProcessSheetProduct>()
                        .eq(ProcessSheetProduct::getProcessSheetId, sheetId)
                        .eq(ProcessSheetProduct::getIsDeleted, 0)
        );

        if (sheetProducts == null || sheetProducts.isEmpty()) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "工艺单无产品明细，无法创建批次");
        }

        // ✅ 构建 productCode -> ProcessSheetProduct 映射
        Map<String, ProcessSheetProduct> productCodeMap = sheetProducts.stream()
                .collect(Collectors.toMap(
                        ProcessSheetProduct::getProductCode,
                        p -> p,
                        (p1, p2) -> p1
                ));

        log.info("✅ 工艺单产品明细缓存: processSheetId={}, 产品数量={}", sheetId, productCodeMap.size());

        // ========== 2. 查询工序模板（用于复制到 process_batch_step） ==========
        List<ProcessSheetStep> templateSteps = processSheetStepMapper.selectList(
                new QueryWrapper<ProcessSheetStep>()
                        .eq("process_sheet_id", sheetId)
                        .eq("is_deleted", 0)
                        .orderByAsc("sequence_no", "id")
        );

        LocalDateTime now = LocalDateTime.now();

        // ========== 3. 遍历批次计划 ==========
        for (ProcessSheetAddRequest.BatchPlanRequest batchReq : batches) {
            // 3.1 保存批次计划（process_sheet_batch）
            ProcessSheetBatch batch = new ProcessSheetBatch();
            batch.setProcessSheetId(sheetId);
            batch.setBatchNo(batchReq.getBatchNo());
            processSheetBatchMapper.insert(batch);
            Long batchPlanId = batch.getId();

            log.info("批次计划保存成功: processSheetId={}, batchNo={}, batchPlanId={}",
                    sheetId, batchReq.getBatchNo(), batchPlanId);

            // ========== 🔧 3.2 修复：保存批次产品明细（使用正确的 product_id） ==========
            if (batchReq.getProducts() != null && !batchReq.getProducts().isEmpty()) {
                for (ProcessSheetAddRequest.BatchProductRequest productReq : batchReq.getProducts()) {

                    // ✅ 根据 productCode 查找正确的 product_id
                    ProcessSheetProduct correctProduct = productCodeMap.get(productReq.getProductCode());

                    if (correctProduct == null) {
                        log.error("❌ 批次产品在工艺单中不存在: batchNo={}, productCode={}, processSheetId={}",
                                batchReq.getBatchNo(), productReq.getProductCode(), sheetId);
                        throw new BusinessException(ErrorCode.PARAMS_ERROR,
                                "产品不存在：" + productReq.getProductCode() + "，请检查工艺单产品明细");
                    }

                    ProcessSheetBatchProduct batchProduct = new ProcessSheetBatchProduct();
                    batchProduct.setBatchId(batchPlanId);
                    batchProduct.setProcessSheetId(sheetId);

                    // ✅ 使用正确的 product_id（从 process_sheet_product 表查询）
                    batchProduct.setProductId(correctProduct.getId());

                    batchProduct.setProductCode(productReq.getProductCode());
                    batchProduct.setProductName(productReq.getProductName());
                    batchProduct.setProductionQuantity(productReq.getProductionQuantity());

                    int inserted = processSheetBatchProductMapper.insert(batchProduct);
                    ThrowUtils.throwIf(inserted != 1, ErrorCode.OPERATION_ERROR, "保存批次产品失败");

                    log.info("✅ 批次产品保存成功: batchPlanId={}, productCode={}, correctProductId={}, productionQty={}",
                            batchPlanId, productReq.getProductCode(), correctProduct.getId(), productReq.getProductionQuantity());
                }
                log.info("批次产品明细保存成功: batchPlanId={}, 产品数量={}",
                        batchPlanId, batchReq.getProducts().size());
            }

            // ========== 3.3 获取产品名称（用于生成产品代号） ==========
            String productName = null;
            Integer totalQuantity = 0;

            if (batchReq.getProducts() != null && !batchReq.getProducts().isEmpty()) {
                productName = batchReq.getProducts().get(0).getProductName();
                totalQuantity = batchReq.getProducts().stream()
                        .mapToInt(ProcessSheetAddRequest.BatchProductRequest::getProductionQuantity)
                        .sum();
            }

            if (StringUtils.isBlank(productName)) {
                log.warn("⚠️ 批次无产品信息，使用工艺单产品名称: batchNo={}", batchReq.getBatchNo());
                productName = sheet.getProductName();
                if (StringUtils.isBlank(productName)) {
                    productName = "未知产品";
                }
            }

            // ========== 3.4 暂不生成批次编码（等待PMC提交时生成） ==========
            log.info("工艺单创建阶段，批次编码稍后由PMC提交时生成: batchNo={}", batchReq.getBatchNo());

            // ========== 3.5 创建批次记录 ==========
            ProcessBatch processBatch = new ProcessBatch();
            processBatch.setProcessSheetId(sheetId);
            processBatch.setBatchPlanId(batchPlanId);
            processBatch.setSourceType(sheet.getSourceType());
            processBatch.setSourceId(sheet.getSourceId());
            processBatch.setSourceNo(sheet.getSourceNo());
            processBatch.setOrderType(sheet.getSourceType() == 1 ? "SALES" : "RDO");
            processBatch.setOrderNumber(sheet.getSourceNo());
            processBatch.setProductName(productName);
            processBatch.setBatchCode(null);
            processBatch.setFlowNumber(null);
            processBatch.setProductionPlanNumber(null);
            processBatch.setQuantity(totalQuantity);
            processBatch.setDeviceId(sheet.getDeviceId());
            processBatch.setDeviceCode(sheet.getDeviceCode());
            processBatch.setDeviceName(sheet.getDeviceName());
            processBatch.setStatus("WAITING_RELEASE");
            processBatch.setCreatedBy(creatorId);
            processBatch.setCreatedByName(creatorName);
            processBatch.setCreateTime(now);
            processBatch.setUpdateTime(now);
            processBatch.setIsDeleted(0);

            processBatchMapper.insert(processBatch);
            Long processBatchId = processBatch.getId();

            log.info("✅ 批次记录创建成功（暂无编号）: processBatchId={}, deviceName={}",
                    processBatchId, sheet.getDeviceName());

            // ========== 3.6 复制工序到 process_batch_step ==========
            if (templateSteps != null && !templateSteps.isEmpty()) {
                for (ProcessSheetStep templateStep : templateSteps) {
                    ProcessBatchStep batchStep = new ProcessBatchStep();
                    batchStep.setBatchId(processBatchId);
                    batchStep.setProcessSheetStepId(templateStep.getId());
                    batchStep.setSequenceNo(templateStep.getSequenceNo());
                    batchStep.setOperationId(templateStep.getOperationId());
                    batchStep.setProcessRequirement(templateStep.getProcessRequirement());
                    batchStep.setInspectionMethod(templateStep.getInspectionMethod());
                    batchStep.setInspectionCriteria(templateStep.getInspectionCriteria());
                    batchStep.setOperationCode(templateStep.getOperationCode());
                    batchStep.setOperationName(templateStep.getOperationName());
                    batchStep.setNeedInspection(
                            templateStep.getIsInspectionRequired() != null
                                    ? templateStep.getIsInspectionRequired()
                                    : 0
                    );
                    batchStep.setStatus("PLANNED");
                    batchStep.setCreateTime(now);
                    batchStep.setUpdateTime(now);
                    batchStep.setIsDeleted(0);

                    processBatchStepMapper.insert(batchStep);
                }

                log.info("✅ 批次工序复制成功: processBatchId={}, 工序数量={}",
                        processBatchId, templateSteps.size());
            }
        }

        log.info("✅ 批次计划保存完成: processSheetId={}, 批次数量={}", sheetId, batches.size());
    }

    @Override
    public void validProcessSheet(ProcessSheet processSheet, boolean add) {
        if (processSheet == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "工艺单信息不能为空");
        }

        String processSheetNo = processSheet.getProcessSheetNo();

        if (StringUtils.isNotBlank(processSheetNo)) {
            ThrowUtils.throwIf(processSheetNo.length() > 50, ErrorCode.PARAMS_ERROR, "工艺单号过长");
        }
    }

    @Override
    public QueryWrapper<ProcessSheet> getQueryWrapper(ProcessSheetQueryRequest processSheetQueryRequest) {
        QueryWrapper<ProcessSheet> queryWrapper = new QueryWrapper<>();
        if (processSheetQueryRequest == null) {
            return queryWrapper;
        }

        String processSheetNo = processSheetQueryRequest.getProcessSheetNo();
        String productCode = processSheetQueryRequest.getProductCode();
        String sortField = processSheetQueryRequest.getSortField();
        String sortOrder = processSheetQueryRequest.getSortOrder();

        queryWrapper.like(StringUtils.isNotBlank(processSheetNo), "process_sheet_no", processSheetNo);
        queryWrapper.like(StringUtils.isNotBlank(productCode), "product_code", productCode);

        // 仅查询未删除的记录
        queryWrapper.eq("is_deleted", 0);

        // 排序逻辑
        if (StringUtils.isNotBlank(sortField)) {
            // 如果前端传了排序字段，优先使用前端排序
            queryWrapper.orderBy(SqlUtils.validSortField(sortField),
                    sortOrder != null && sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                    sortField);
        } else {
            // ✅【修改】默认按创建时间倒序排列 (最新创建的在最前)
            queryWrapper.orderByDesc("create_time");
        }

        return queryWrapper;
    }

    @Override
    public ProcessSheetVO getProcessSheetVO(ProcessSheet processSheet) {
        if (processSheet == null) {
            return null;
        }

        ProcessSheetVO vo = new ProcessSheetVO();

        // ========== 基本字段复制 ==========
        vo.setId(String.valueOf(processSheet.getId()));
        vo.setProcessSheetNo(processSheet.getProcessSheetNo());
        vo.setProcessVersion(processSheet.getProcessVersion());

        // ✅ 拼接完整工艺单号
        vo.setFullProcessSheetNo(buildFullProcessSheetNo(
                processSheet.getProcessSheetNo(),
                processSheet.getProcessVersion()
        ));

        vo.setSourceType(processSheet.getSourceType());
        vo.setSourceId(processSheet.getSourceId() != null ? String.valueOf(processSheet.getSourceId()) : null);
        vo.setSourceNo(processSheet.getSourceNo());
        vo.setQuantity(processSheet.getQuantity());
        vo.setProductCount(processSheet.getProductCount());
        vo.setMaterialBrand(processSheet.getMaterialBrand());
        vo.setMaterialSpec(processSheet.getMaterialSpec());
        vo.setPowderSupplier(processSheet.getPowderSupplier());
        vo.setPlannedBatchCount(processSheet.getPlannedBatchCount());
        vo.setDueDate(processSheet.getDueDate());
        vo.setDeviceName(processSheet.getDeviceName());
        if (processSheet.getDeviceId() != null) {
            vo.setDeviceId(String.valueOf(processSheet.getDeviceId()));
        }
        vo.setCustomerName(processSheet.getCustomerName());
        vo.setProcessStatus(processSheet.getProcessStatus());
        vo.setTechnicalRequirements(processSheet.getTechnicalRequirements());
        vo.setProcessFileIds(processSheet.getProcessFileIds());
        vo.setCreatedBy(processSheet.getCreatedBy() != null ? String.valueOf(processSheet.getCreatedBy()) : null);
        vo.setCreatedByName(processSheet.getCreatedByName());
        vo.setDepartmentId(processSheet.getDepartmentId() != null ? String.valueOf(processSheet.getDepartmentId()) : null);
        vo.setCreateTime(processSheet.getCreateTime());
        vo.setUpdateTime(processSheet.getUpdateTime());

        try {
            Long sheetId = processSheet.getId();

            // ========== 查询产品明细 ==========
            List<ProcessSheetProduct> products = processSheetProductMapper.selectList(
                    new QueryWrapper<ProcessSheetProduct>()
                            .eq("process_sheet_id", sheetId)
                            .eq("is_deleted", 0)
                            .orderByAsc("sort_order", "id")
            );

            if (products != null && !products.isEmpty()) {
                List<ProcessSheetVO.ProductDetailVO> productVOs = products.stream().map(p -> {
                    ProcessSheetVO.ProductDetailVO pvo = new ProcessSheetVO.ProductDetailVO();
                    pvo.setId(String.valueOf(p.getId()));
                    pvo.setProcessSheetId(String.valueOf(p.getProcessSheetId()));
                    pvo.setOrderItemId(p.getOrderItemId() != null ? String.valueOf(p.getOrderItemId()) : null);
                    pvo.setProductCode(p.getProductCode());
                    pvo.setProductName(p.getProductName());
                    pvo.setSpecification(p.getSpecification());
                    pvo.setQuantity(p.getQuantity());
                    pvo.setPartDrawingNo(p.getPartDrawingNo());
                    pvo.setUnitWeight(p.getUnitWeight());
                    pvo.setProductHeight(p.getProductHeight());
                    pvo.setSortOrder(p.getSortOrder());
                    pvo.setRemark(p.getRemark());
                    pvo.setCreateTime(p.getCreateTime());
                    pvo.setUpdateTime(p.getUpdateTime());
                    return pvo;
                }).collect(Collectors.toList());
                vo.setProducts(productVOs);
            }

            // ========== 查询工序步骤 ==========
            List<ProcessSheetStep> steps = processSheetStepMapper.selectList(
                    new QueryWrapper<ProcessSheetStep>()
                            .eq("process_sheet_id", sheetId)
                            .eq("is_deleted", 0)
                            .orderByAsc("sequence_no", "id")
            );
            vo.setSteps(steps);

            // ========== 查询批次计划 ==========
            List<ProcessSheetBatch> batches = processSheetBatchMapper.selectList(
                    new QueryWrapper<ProcessSheetBatch>()
                            .eq("process_sheet_id", sheetId)
                            .eq("is_deleted", 0)
                            .orderByAsc("batch_no")
            );

            if (batches != null && !batches.isEmpty()) {
                List<ProcessSheetVO.BatchPlanVO> batchVOs = batches.stream().map(batch -> {
                    ProcessSheetVO.BatchPlanVO bvo = new ProcessSheetVO.BatchPlanVO();
                    bvo.setId(String.valueOf(batch.getId()));
                    bvo.setProcessSheetId(String.valueOf(batch.getProcessSheetId()));
                    bvo.setBatchNo(batch.getBatchNo());
                    bvo.setCreateTime(batch.getCreateTime());
                    bvo.setUpdateTime(batch.getUpdateTime());

                    // 查询批次产品明细
                    List<ProcessSheetBatchProduct> batchProducts = processSheetBatchProductMapper.selectList(
                            new QueryWrapper<ProcessSheetBatchProduct>()
                                    .eq("batch_id", batch.getId())
                                    .eq("is_deleted", 0)
                    );

                    if (batchProducts != null && !batchProducts.isEmpty()) {
                        List<ProcessSheetVO.BatchProductVO> bpVOs = batchProducts.stream().map(bp -> {
                            ProcessSheetVO.BatchProductVO bpvo = new ProcessSheetVO.BatchProductVO();
                            bpvo.setId(String.valueOf(bp.getId()));
                            bpvo.setBatchId(String.valueOf(bp.getBatchId()));
                            bpvo.setProcessSheetId(String.valueOf(bp.getProcessSheetId()));
                            bpvo.setProductId(String.valueOf(bp.getProductId()));
                            bpvo.setProductCode(bp.getProductCode());
                            bpvo.setProductName(bp.getProductName());
                            bpvo.setProductionQuantity(bp.getProductionQuantity());
                            bpvo.setCreateTime(bp.getCreateTime());
                            bpvo.setUpdateTime(bp.getUpdateTime());
                            return bpvo;
                        }).collect(Collectors.toList());
                        bvo.setProducts(bpVOs);
                    }

                    return bvo;
                }).collect(Collectors.toList());
                vo.setBatches(batchVOs);
            }

        } catch (Exception e) {
            log.error("查询工艺单明细失败: processSheetId={}", processSheet.getId(), e);
        }

        return vo;
    }

    @Override
    public Page<ProcessSheetVO> getProcessSheetVOPage(Page<ProcessSheet> processSheetPage) {
        List<ProcessSheet> processSheetList = processSheetPage.getRecords();
        Page<ProcessSheetVO> processSheetVOPage = new Page<>(processSheetPage.getCurrent(), processSheetPage.getSize(), processSheetPage.getTotal());
        if (processSheetList.isEmpty()) {
            return processSheetVOPage;
        }
        List<ProcessSheetVO> processSheetVOList = processSheetList.stream()
                .map(this::getProcessSheetVO)
                .collect(Collectors.toList());
        processSheetVOPage.setRecords(processSheetVOList);
        return processSheetVOPage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateWithSteps(ProcessSheetUpdateRequest request, Long updaterId, String updaterName) {
        if (request == null || StringUtils.isBlank(request.getId())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "工艺单ID不能为空");
        }

        Long sheetId;
        try {
            sheetId = Long.parseLong(request.getId());
        } catch (NumberFormatException e) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "工艺单ID格式错误");
        }

        // ========== 1. 验证工艺单是否存在 ==========
        ProcessSheet existingSheet = this.getById(sheetId);
        if (existingSheet == null || existingSheet.getIsDeleted() == 1) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "工艺单不存在或已删除");
        }

        log.info("========== 开始更新工艺单 ==========");
        log.info("processSheetId={}, updater={}, updaterName={}", sheetId, updaterId, updaterName);

        // ========== 2. 构建更新对象（仅基础字段）==========
        ProcessSheet processSheet = new ProcessSheet();
        processSheet.setId(sheetId);

        // 材料信息
        if (StringUtils.isNotBlank(request.getMaterialBrand())) {
            processSheet.setMaterialBrand(request.getMaterialBrand());
            log.debug("更新材料品牌: {}", request.getMaterialBrand());
        }

        if (StringUtils.isNotBlank(request.getMaterialSpec())) {
            processSheet.setMaterialSpec(request.getMaterialSpec());
            log.debug("更新材料规格: {}", request.getMaterialSpec());
        }

        if (StringUtils.isNotBlank(request.getPowderSupplier())) {
            processSheet.setPowderSupplier(request.getPowderSupplier());
            log.debug("更新粉末供应商: {}", request.getPowderSupplier());
        }

        // 交期
        if (request.getDueDate() != null) {
            processSheet.setDueDate(request.getDueDate());
            log.debug("更新交期: {}", request.getDueDate());
        }

        // 技术要求
        if (StringUtils.isNotBlank(request.getTechnicalRequirements())) {
            processSheet.setTechnicalRequirements(request.getTechnicalRequirements());
            log.debug("更新技术要求: {}", request.getTechnicalRequirements());
        }

        // 工艺文件
        if (StringUtils.isNotBlank(request.getProcessFileIds())) {
            processSheet.setProcessFileIds(request.getProcessFileIds());
            log.debug("更新工艺文件: {}", request.getProcessFileIds());
        }

        // 设备信息（仅名称，不涉及设备ID变更）
        if (StringUtils.isNotBlank(request.getDeviceName())) {
            processSheet.setDeviceName(request.getDeviceName());
            log.debug("更新设备名称: {}", request.getDeviceName());
        }

        // 客户名称
        if (StringUtils.isNotBlank(request.getCustomerName())) {
            processSheet.setCustomerName(request.getCustomerName());
            log.debug("更新客户名称: {}", request.getCustomerName());
        }

        // 更新时间
        processSheet.setUpdateTime(LocalDateTime.now());

        // ========== 3. ✅ 允许更新产品明细的工艺字段 ==========
        if (request.getProducts() != null && !request.getProducts().isEmpty()) {
            log.info("开始更新产品明细的工艺字段: processSheetId={}, 产品数量={}", sheetId, request.getProducts().size());

            int updatedCount = 0;
            int skippedCount = 0;

            for (ProcessSheetUpdateRequest.ProductDetailRequest reqProduct : request.getProducts()) {
                // 校验产品ID
                if (reqProduct.getId() == null || reqProduct.getId().trim().isEmpty()) {
                    log.warn("跳过无效产品记录（缺少ID）");
                    skippedCount++;
                    continue;
                }

                Long productId;
                try {
                    productId = Long.parseLong(reqProduct.getId());
                } catch (NumberFormatException e) {
                    log.warn("跳过无效产品ID: {}", reqProduct.getId());
                    skippedCount++;
                    continue;
                }

                // ✅ 只更新工艺字段，不更新订单字段
                ProcessSheetProduct updateProduct = new ProcessSheetProduct();
                updateProduct.setId(productId);
                updateProduct.setPartDrawingNo(reqProduct.getPartDrawingNo());
                updateProduct.setUnitWeight(reqProduct.getUnitWeight());
                updateProduct.setProductHeight(reqProduct.getProductHeight());
                updateProduct.setUpdateTime(LocalDateTime.now());

                int rows = processSheetProductMapper.updateById(updateProduct);

                if (rows > 0) {
                    updatedCount++;
                    log.info("✅ 产品工艺字段更新成功: productId={}, productName={}, partDrawingNo={}, unitWeight={}, productHeight={}",
                            productId,
                            reqProduct.getProductName(),
                            reqProduct.getPartDrawingNo(),
                            reqProduct.getUnitWeight(),
                            reqProduct.getProductHeight());
                } else {
                    skippedCount++;
                    log.warn("⚠️ 产品更新失败（可能不存在或已删除）: productId={}", productId);
                }
            }

            log.info("产品明细工艺字段更新完成: 成功={}, 跳过={}", updatedCount, skippedCount);
        }

        // ========== 4. 忽略工序步骤的修改 ==========
        if (request.getSteps() != null && !request.getSteps().isEmpty()) {
            log.warn("⚠️ 工艺单更新时传入了工序步骤数据，已忽略: processSheetId={}, 工序数量={}, updater={}",
                    sheetId, request.getSteps().size(), updaterName);
            log.warn("提示: 工艺单发布后，工序已同步到批次，不允许修改。如需调整，请重新创建工艺单");
        }

        // ========== 5. 忽略批次计划的修改 ==========
        if (request.getBatches() != null && !request.getBatches().isEmpty()) {
            log.warn("⚠️ 工艺单更新时传入了批次计划数据，已忽略: processSheetId={}, 批次数量={}, updater={}",
                    sheetId, request.getBatches().size(), updaterName);
            log.warn("提示: 批次由PMC部门管理，工程部不能修改。如需调整批次，请联系PMC部门");
        }

        // ========== 6. 忽略版本号修改 ==========
        if (StringUtils.isNotBlank(request.getProcessVersion())
                && !request.getProcessVersion().equals(existingSheet.getProcessVersion())) {
            log.warn("⚠️ 工艺单更新时尝试修改版本号，已忽略: processSheetId={}, 原版本={}, 新版本={}, updater={}",
                    sheetId, existingSheet.getProcessVersion(), request.getProcessVersion(), updaterName);
            log.warn("提示: 版本号不允许修改。如需新版本，请重新创建工艺单");
        }

        // ========== 7. 执行基础信息更新 ==========
        validProcessSheet(processSheet, false);
        boolean updateResult = this.updateById(processSheet);

        if (updateResult) {
            log.info("✅ 工艺单更新成功: processSheetId={}, updater={}, updaterName={}",
                    sheetId, updaterId, updaterName);
        } else {
            log.error("❌ 工艺单基础信息更新失败: processSheetId={}, updater={}", sheetId, updaterId);
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "工艺单更新失败");
        }

        return updateResult;
    }
    // ==================== 工具方法 ====================

    /**
     * ✅ 拼接完整工艺单号（基础号 + 版本号）
     */
    private String buildFullProcessSheetNo(String baseNo, String version) {
        if (StringUtils.isBlank(baseNo)) {
            return "";
        }
        String ver = StringUtils.isNotBlank(version) ? version : "A";
        return baseNo + "-" + ver;
    }

    /**
     * ✅ 生成工艺单基础号（不含版本后缀）：GY + yyMMdd + 3位序号
     */
    private String generateProcessSheetBaseNo() {
        LocalDateTime now = LocalDateTime.now();
        String dayPart = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMMdd"));
        String monthPart = now.format(java.time.format.DateTimeFormatter.ofPattern("yyMM"));
        String monthPrefix = "GY" + monthPart;

        QueryWrapper<ProcessSheet> maxQ = new QueryWrapper<>();
        maxQ.select("MAX(CAST(SUBSTRING(process_sheet_no, -3) AS UNSIGNED)) AS maxSeq")
                .likeRight("process_sheet_no", monthPrefix);

        int maxSeq = 0;
        try {
            List<Map<String, Object>> maps = this.baseMapper.selectMaps(maxQ);
            if (maps != null && !maps.isEmpty() && maps.get(0) != null) {
                Map<String, Object> row0 = maps.get(0);
                Object val = row0.get("maxSeq");
                if (val != null) {
                    maxSeq = Integer.parseInt(String.valueOf(val));
                }
            }
        } catch (Exception ignored) {
        }

        if (maxSeq == 0) {
            QueryWrapper<ProcessSheet> scanQ = new QueryWrapper<>();
            scanQ.select("process_sheet_no")
                    .likeRight("process_sheet_no", monthPrefix);
            List<ProcessSheet> existing = this.list(scanQ);
            if (existing != null) {
                for (ProcessSheet ps : existing) {
                    String no = ps.getProcessSheetNo();
                    if (StringUtils.isBlank(no)) continue;
                    if (no.length() < 11) continue; // GY + yyMMdd + NNN
                    String dayInNo = no.substring(2, 8); // yyMMdd
                    String monthInNo = dayInNo.substring(0, 4);
                    if (!monthPart.equals(monthInNo)) continue;
                    String seqStr = no.substring(no.length() - 3);
                    if (StringUtils.isNumeric(seqStr)) {
                        int seq = Integer.parseInt(seqStr);
                        if (seq > maxSeq) {
                            maxSeq = seq;
                        }
                    }
                }
            }
        }

        int nextSeq = Math.min(maxSeq + 1, 999);
        String seq3 = String.format("%03d", nextSeq);
        return "GY" + dayPart + seq3;
    }

    // ==================== 其他方法 ====================

    @Override
    public ProcessSheet getBySourceId(Integer sourceType, Long sourceId) {
        log.warn("getBySourceId方法需要根据新的业务逻辑重新实现");
        return null;
    }

    private String getProductNameSummary(Long salesOrderId) {
        try {
            List<SalesOrderItem> items = salesOrderItemMapper.selectList(
                    new LambdaQueryWrapper<SalesOrderItem>()
                            .eq(SalesOrderItem::getOrderId, salesOrderId)
                            .eq(SalesOrderItem::getIsDeleted, 0)
                            .orderByAsc(SalesOrderItem::getSortOrder)
            );

            if (items == null || items.isEmpty()) {
                return "未知产品";
            }

            if (items.size() == 1) {
                return items.get(0).getProductName();
            }

            String firstName = items.get(0).getProductName();
            return firstName + "等" + items.size() + "种产品";

        } catch (Exception e) {
            log.error("获取产品名称摘要失败: orderId={}", salesOrderId, e);
            return "未知产品";
        }
    }

    @Override
    public boolean checkCreatePermission(Long salesOrderId, Long userId) {
        if (salesOrderId == null || userId == null) {
            return false;
        }

        try {
            QueryWrapper<WorkflowTask> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("bizType", "SALES_ORDER")
                    .eq("bizId", salesOrderId)
                    .eq("taskType", "CREATE_PROCESS_SHEET")
                    .eq("claimedBy", userId)
                    .eq("taskStatus", "IN_PROGRESS")
                    .eq("isDelete", 0);

            WorkflowTask task = workflowTaskMapper.selectOne(queryWrapper);
            boolean hasPermission = task != null;

            log.info("检查工艺单创建权限: salesOrderId={}, userId={}, hasPermission={}", salesOrderId, userId, hasPermission);
            return hasPermission;
        } catch (Exception e) {
            log.error("检查工艺单创建权限失败: salesOrderId={}, userId={}", salesOrderId, userId, e);
            return false;
        }
    }

    @Override
    public void handleProcessSheetPublished(Long salesOrderId, String orderNumber, String productCode, Long creatorId, String creatorName) {
        try {
            log.info("开始处理工艺单发布后续逻辑: orderId={}, orderNumber={}", salesOrderId, orderNumber);

            boolean updated = salesOrderService.updateOrderStatus(salesOrderId, "ROUTING_PUBLISHED", "工艺已发布", 20.0);
            log.info("更新销售订单状态: orderId={}, success={}", salesOrderId, updated);

            String productName = getProductNameFromSalesOrder(salesOrderId);

            workflowTaskService.createScheduleTaskForSalesOrder(
                    salesOrderId,
                    orderNumber,
                    productName != null ? productName : productCode,
                    creatorId,
                    creatorName
            );

            log.info("工艺单发布后续处理完成: orderId={}, orderNumber={}", salesOrderId, orderNumber);
        } catch (Exception e) {
            log.error("工艺单发布后续处理失败: orderId={}, orderNumber={}", salesOrderId, orderNumber, e);
        }
    }

    private String getProductNameFromSalesOrder(Long salesOrderId) {
        try {
            SalesOrder salesOrder = salesOrderService.getById(salesOrderId);
            if (salesOrder == null) {
                return null;
            }

            List<SalesOrderItem> orderItems = salesOrderItemMapper.selectList(
                    new LambdaQueryWrapper<SalesOrderItem>()
                            .eq(SalesOrderItem::getOrderId, salesOrderId)
                            .eq(SalesOrderItem::getIsDeleted, 0)
                            .orderByAsc(SalesOrderItem::getSortOrder)
                            .last("LIMIT 1")
            );

            if (orderItems != null && !orderItems.isEmpty()) {
                return orderItems.get(0).getProductName();
            }

            return null;
        } catch (Exception e) {
            log.error("获取销售订单产品名称失败: orderId={}", salesOrderId, e);
            return null;
        }
    }

    @Override
    public List<ProcessSheetExcelStepDTO> parseProcessSheetExcel(MultipartFile file) {
        return documentParseService.parse(file);
    }

    @Override
    public List<ProcessSheetExcelStepDTO> parseProcessSheetExcel(File file, String originalFilename) {
        return documentParseService.parse(file, originalFilename);
    }

    @Override
    public Map<String, Object> queryProcessSheetByProductName(String productName) {
        if (StringUtils.isBlank(productName)) {
            throw new IllegalArgumentException("产品名称不能为空");
        }

        QueryWrapper<ProcessSheet> qw = new QueryWrapper<>();
        qw.eq("product_name", productName)
                .orderByDesc("create_time")
                .last("LIMIT 1");

        ProcessSheet processSheet = this.baseMapper.selectOne(qw);

        Map<String, Object> result = new HashMap<>();
        if (processSheet != null) {
            result.put("processSheetId", String.valueOf(processSheet.getId()));
            result.put("processSheetNo", buildFullProcessSheetNo(processSheet.getProcessSheetNo(), processSheet.getProcessVersion()));
            result.put("productName", processSheet.getProductName());
            result.put("productCode", processSheet.getProductCode());
            result.put("salesOrderId", processSheet.getSourceId() != null ? String.valueOf(processSheet.getSourceId()) : null);
            result.put("orderNumber", processSheet.getSourceNo());
            result.put("productSpec", processSheet.getSpecification());
        } else {
            result.put("processSheetId", null);
            result.put("processSheetNo", null);
            result.put("productName", productName);
            result.put("productCode", null);
            result.put("salesOrderId", null);
            result.put("orderNumber", null);
            result.put("productSpec", null);
        }

        return result;
    }

    @Override
    public Long getProcessSheetIdByOrderId(Long orderId, Integer sourceType) {
        if (orderId == null || orderId <= 0) {
            return null;
        }

        if (sourceType == null) {
            sourceType = 1;
        }

        try {
            QueryWrapper<ProcessSheet> qw = new QueryWrapper<>();
            qw.eq("source_type", sourceType)
                    .eq("source_id", orderId)
                    .eq("is_deleted", 0)
                    .orderByDesc("create_time")
                    .last("LIMIT 1");

            ProcessSheet processSheet = this.baseMapper.selectOne(qw);

            if (processSheet != null) {
                log.info("根据订单ID查询工艺单成功: orderId={}, sourceType={}, processSheetId={}",
                        orderId, sourceType, processSheet.getId());
                return processSheet.getId();
            } else {
                log.warn("根据订单ID未找到工艺单: orderId={}, sourceType={}", orderId, sourceType);
                return null;
            }

        } catch (Exception e) {
            log.error("根据订单ID查询工艺单失败: orderId={}, sourceType={}", orderId, sourceType, e);
            return null;
        }
    }
}